using System;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Ardalis.ApiEndpoints.CodeAnalyzers
{
    [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(EndpointHasExtraPublicMethodCodeFixProvider)), Shared]
    public class EndpointHasExtraPublicMethodCodeFixProvider : CodeFixProvider
    {
        private const string MakeInternalTitle = "Make additonal method internal.";
        private const string MakePrivateTitle = "Make additonal method private.";

        public sealed override ImmutableArray<string> FixableDiagnosticIds
        {
            get { return ImmutableArray.Create(EndpointHasExtraPublicMethodAnalyzer.DiagnosticId); }
        }

        public sealed override FixAllProvider GetFixAllProvider()
        {
            // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
            return WellKnownFixAllProviders.BatchFixer;
        }

        public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

            var diagnostic = context.Diagnostics.First();
            var diagnosticSpan = diagnostic.Location.SourceSpan;

            // Find the method declaration identified by the diagnostic.
            var declaration = 
                root
                    .FindToken(diagnosticSpan.Start)
                    .Parent
                    .AncestorsAndSelf()
                    .OfType<MethodDeclarationSyntax>()
                    .First();
            
            // Register a code action that will invoke the fix.
            context.RegisterCodeFix(
                CodeAction.Create(
                    title: MakeInternalTitle,
                    createChangedDocument: c => ChangeMethodKindAsync(context.Document, declaration, SyntaxKind.InternalKeyword, c),
                    equivalenceKey: MakeInternalTitle),
                diagnostic);

            context.RegisterCodeFix(
                CodeAction.Create(
                    title: MakePrivateTitle,
                    createChangedDocument: c => ChangeMethodKindAsync(context.Document, declaration, SyntaxKind.PrivateKeyword, c),
                    equivalenceKey: MakePrivateTitle),
                diagnostic);
        }

        private async Task<Document> ChangeMethodKindAsync(
            Document document,
            MethodDeclarationSyntax method,
            SyntaxKind targetKind,
            CancellationToken cancellationToken)
        {
            try
            {
                var modifierList =
                    // create the new modifier list, but replace the public token
                    // goal is to preserve order
                    method
                        .Modifiers
                        .Select(x => 
                            x.Kind() is SyntaxKind.PublicKeyword 
                                ? SyntaxFactory.Token(targetKind) 
                                : x)
                        .ToList();

                // remove the public modifier
                var newMethod = method.WithModifiers(new SyntaxTokenList(modifierList));

                var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
                var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false);

                var newRoot = root.ReplaceNode(method, newMethod);

                return document.WithSyntaxRoot(newRoot);
            }
            catch (Exception e)
            {
                Debug.Write(e);

                if (Debugger.IsAttached)
                    throw;

                return document;
            }
        }
    }
}
